/******************************************************************************
 * * hal_ble.c - realize ble functions in hal
 *
 * *(C) Copyright 2021 Asr International Ltd.
 * * All Rights Reserved
 * ******************************************************************************/

/*********************
 *      INCLUDES
 *********************/

#include "hal_ble_profile.h"

#if USE_LV_BLE_DEMO != 0

/*********************
 *      DEFINES
 *********************/
#define BLE_DEV            (0) // for a peer device if 1

#if BLE_DEV == 0
#define BLE_DEV_NAME       ("ASR BLE DEMO 1")
#define BLE_DEV_SCAN_NAME  ("ASR BLE DEMO 2")
#else
#define BLE_DEV_NAME       ("ASR BLE DEMO 2")
#define BLE_DEV_SCAN_NAME  ("ASR BLE DEMO 1")
#endif

/**********************
 *      TYPEDEFS
 **********************/
enum {
    BLE_SERVICE_DISCOVER_NULL = 0,
    BLE_SERVICE_DISCOVER_MTU,
    BLE_SERVICE_DISCOVER_PRIMARY,
    BLE_SERVICE_DISCOVER_CHARACTERISTIC,
    BLE_SERVICE_DISCOVER_CCC,
    BLE_SERVICE_DISCOVER_COMPLETE,
};
typedef uint8_t BLE_SERVICE_DISCOVER_T;

typedef struct {
    BLE_STATE_T state;
    ble_att_event_handle_cb event_cb[BLE_PROFILE_MAX];
    ble_deinit_cb deinit_cb[BLE_PROFILE_MAX];
    bool is_slave;
    struct bt_task_event conn_msg;
    struct bt_event_le_att_connected conn_info; // conn_msg.payload

    // for server
    ble_get_serv_cb get_cb[BLE_PROFILE_MAX];
    void * fast_conn_timer;
    bool is_service_present;

    // for client
    void * fast_scan_timer;
    uint32_t profile_bitmap; // bitmap for the profiles which need to establish the connection
    BLE_SERVICE_DISCOVER_T service_discover_state;
    lv_ll_t record;          // ble_service_record_t
    ble_service_record_t * service;
    ble_characteristic_record_t * characteristic;
} ble_global_t;

/**********************
 *  STATIC PROTOTYPES
 **********************/
static uint16_t ble_get_service_changed_handle(void);
static void ble_service_record_free(void);
static void ble_init_profiles(void);
static void ble_set_adv_data(void);
static void ble_set_adv_param(uint16_t min_interval, uint16_t max_interval);
static void ble_register_service(void);
static int32_t ble_att_event_handle(struct bt_task_event * msg);
static void ble_scan_event_handle(struct bt_event_le_scan_event * scan);
static void ble_start_fast_conn_timer(void);
static void ble_fast_scan_timer_cb(uint32_t param);

/**********************
 *  STATIC VARIABLES
 **********************/
static ble_global_t * ble_global = NULL;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
***********************/
void ble_init(void)
{
    if(ble_global) {
        printf("%s: ble has been initiated\n", __FUNCTION__);
        return;
    }

    appbt_le_set_adv_enable(0);
    appbt_le_unregister_service();

    ble_global = Hal_Mem_Alloc(sizeof(ble_global_t));
    Hal_Mem_Set(ble_global, 0, sizeof(ble_global_t));

    _lv_ll_init(&ble_global->record, sizeof(ble_service_record_t));

    ble_init_profiles();

    appbt_le_register_att_event_handle(ble_att_event_handle);

    ble_set_adv_data();

    ble_register_service();

    printf("%s: is_service_present %d\n", __FUNCTION__, ble_global->is_service_present);

    ble_global->state = BLE_STATE_IDLE;

    if(!ble_global->is_service_present) return;

    ble_set_adv_param(20, 30);
}

void ble_add_serv(void)
{
    if(!ble_global) {
        printf("%s: ble is not initiated\n", __FUNCTION__);
        return;
    }

    printf("%s: state %d, prev is_service_present %d\n",
           __FUNCTION__, ble_global->state, ble_global->is_service_present);

    appbt_le_set_adv_enable(0);
    appbt_le_unregister_service();

    if(ble_global->fast_conn_timer) {
        uos_timer_delete(ble_global->fast_conn_timer);
        ble_global->fast_conn_timer = NULL;
    }

    ble_register_service();

    if(BLE_STATE_IDLE == ble_global->state) {
        ble_set_adv_param(20, 30);
        ble_start_fast_conn_timer();
    } else if((BLE_STATE_CONNECTING == ble_global->state)
              || (BLE_STATE_CONNECTED == ble_global->state)) {
        appbt_le_disconnect();
        ble_global->state = BLE_STATE_DISCONNECTING;
    }
}

void ble_del_serv(void)
{
    if(!ble_global) {
        printf("%s: ble is not initiated\n", __FUNCTION__);
        return;
    }

    if(!ble_global->is_service_present) {
        printf("%s: no service to be deleted\n", __FUNCTION__);
        return;
    }

    appbt_le_set_adv_enable(0);
    appbt_le_unregister_service();

    ble_register_service();

    printf("%s: state %d, is_service_present %d\n",
           __FUNCTION__, ble_global->state, ble_global->is_service_present);

    if(BLE_STATE_IDLE == ble_global->state) {
        if(ble_global->is_service_present) {
            appbt_le_set_adv_enable(1);
        } else {
            if(ble_global->fast_conn_timer) {
                uos_timer_delete(ble_global->fast_conn_timer);
                ble_global->fast_conn_timer = NULL;
            }
        }
    } else if((BLE_STATE_CONNECTING == ble_global->state)
              || (BLE_STATE_CONNECTED == ble_global->state)) {
        appbt_le_disconnect();
        ble_global->state = BLE_STATE_DISCONNECTING;
    }
}

void ble_deinit(void)
{
    if(ble_global) {
        printf("%s\n", __FUNCTION__);
    } else {
        printf("%s: ble is not initiated\n", __FUNCTION__);
        return;
    }

    appbt_le_set_adv_enable(0);
    appbt_le_unregister_service();
    appbt_le_register_att_event_handle(NULL);

    if(ble_global->fast_conn_timer) {
        uos_timer_delete(ble_global->fast_conn_timer);
    }

    if(ble_global->fast_scan_timer) {
        uos_timer_delete(ble_global->fast_scan_timer);
    }

    if((BLE_STATE_CONNECTING == ble_global->state) || (BLE_STATE_CONNECTED == ble_global->state)) {
        appbt_le_disconnect();
    } else if(BLE_STATE_SCAN == ble_global->state) {
        appbt_le_scan_stop();
    }

    ble_service_record_free();

    //inform all profiles of disconnection event
    for(uint8_t i = 0;i < BLE_PROFILE_MAX;i++) {
        if(ble_global->deinit_cb[i]) {
            ble_global->deinit_cb[i]();
        }
    }

    Hal_Mem_Free(ble_global);
    ble_global = NULL;
}

void ble_profile_cb_reg(BLE_PROFILE_T profile, ble_get_serv_cb get_cb,
                        ble_att_event_handle_cb event_cb, ble_deinit_cb deinit_cb)
{
    if(BLE_PROFILE_MAX <= profile) {
        printf("%s: warning, invalid profile %d\n", __FUNCTION__, profile);
        return;
    }

    if(!ble_global) {
        printf("%s: ble is not initiated\n", __FUNCTION__);
        return;
    }

    ble_global->get_cb[profile] = get_cb;
    ble_global->event_cb[profile] = event_cb;
    ble_global->deinit_cb[profile] = deinit_cb;
}

BLE_STATE_T ble_get_state(void)
{
    if(NULL == ble_global) {
        return BLE_STATE_NULL;
    } else {
        return ble_global->state;
    }
}

void ble_conn_est(BLE_PROFILE_T profile)
{
    if(BLE_PROFILE_MAX <= profile) {
        printf("%s: warning, invalid profile %d\n", __FUNCTION__, profile);
        return;
    }

    if(NULL == ble_global) {
        printf("%s: ble_global is NULL\n", __FUNCTION__);
        ble_init();
    }

    printf("%s: ble state %d, profile %d, bitmap %d, discover state %d\n",
           __FUNCTION__, ble_global->state, profile, ble_global->profile_bitmap,
           ble_global->service_discover_state);

    if(BLE_STATE_IDLE == ble_global->state) {
        if(0 != ble_global->profile_bitmap) {
            printf("%s: warning, wrong profile bitmap\n", __FUNCTION__);
            return;
        }

        if(0 == appbt_le_scan(LE_ACTIVE_SCAN,
                              0x60, // interval - 50ms
                              0x30, // windown - 30ms
                              LE_ADDRESS_TYPE_RANDOM,
                              ble_scan_event_handle)) {
            ble_global->state = BLE_STATE_SCAN;

            uos_timer_create(&ble_global->fast_scan_timer);
            uos_timer_start(ble_global->fast_scan_timer, MS_TO_TICKS(30000), 0,
                            ble_fast_scan_timer_cb, 0);
        } else {
            printf("%s: scan failed\n", __FUNCTION__);
        }
    } else if (BLE_STATE_CONNECTED == ble_global->state) {
        if(BLE_SERVICE_DISCOVER_NULL == ble_global->service_discover_state) {
#if 0
            appbt_le_mtu_request(ble_global->conn_info.acl_handle);
            ble_global->service_discover_state = BLE_SERVICE_DISCOVER_MTU;
            printf("%s: start mtu\n", __FUNCTION__);
#else
            appbt_le_read_by_group_type_request(0x0001, 0xffff, LE_ATT_UUID_PRIMARY);
            ble_global->service_discover_state = BLE_SERVICE_DISCOVER_PRIMARY;
            printf("%s: start service discover primary\n", __FUNCTION__);
#endif
        } else if(BLE_SERVICE_DISCOVER_COMPLETE == ble_global->service_discover_state) {
            if(ble_global->event_cb[profile]) {
                ble_global->profile_bitmap |= 1 << profile;
                ble_global->event_cb[profile](&ble_global->conn_msg);
                return;
            }
        }
    }

    ble_global->profile_bitmap |= 1 << profile;
}

void ble_conn_disc(BLE_PROFILE_T profile)
{
    if(BLE_PROFILE_MAX <= profile) {
        printf("%s: warning, invalid profile %d\n", __FUNCTION__, profile);
        return;
    }

    printf("%s: ble state %d, profile %d, bitmap %d, is_slave %d\n",
           __FUNCTION__, ble_global->state, profile, ble_global->profile_bitmap,
           ble_global->is_slave);

    if(0 == (ble_global->profile_bitmap & (1 << profile))) {
        printf("%s: warning, profile didn`t est conn\n", __FUNCTION__);
        return;
    }

    ble_global->profile_bitmap &= ~(1 << profile);

    if((0 != ble_global->profile_bitmap) || ble_global->is_slave) {
        printf("%s: communication with the other profile\n", __FUNCTION__);
#if 0
        if(0 == ble_global->profile_bitmap) {
            ble_service_record_free();
        }
#endif
        return;
    }

    if(ble_global->fast_scan_timer) {
        uos_timer_delete(ble_global->fast_scan_timer);
        ble_global->fast_scan_timer = NULL;
    }
    if(BLE_STATE_SCAN == ble_global->state) {
        appbt_le_scan_stop();
        ble_global->state = BLE_STATE_IDLE;
    } else if((BLE_STATE_CONNECTING == ble_global->state)
              || (BLE_STATE_CONNECTED == ble_global->state)) {
        appbt_le_disconnect();
        ble_global->state = BLE_STATE_DISCONNECTING;
    }
}

lv_ll_t * ble_get_service_record(void)
{
    if(ble_global && (BLE_SERVICE_DISCOVER_COMPLETE == ble_global->service_discover_state)) {
        return &ble_global->record;
    } else {
        return NULL;
    }
}

bool ble_is_profile_process(BLE_PROFILE_T profile)
{
    if(BLE_PROFILE_MAX <= profile) {
        printf("%s: warning, invalid profile %d\n", __FUNCTION__, profile);
        return false;
    }

    printf("%s: profile %d\n", __FUNCTION__, profile);

    if(ble_global && (0 != (ble_global->profile_bitmap & (1 << profile)))) {
        printf("%s: bitmap %d\n", __FUNCTION__, ble_global->profile_bitmap);
        return true;
    }
    return false;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
static void ble_read_service_record_complete(void)
{
    if(BLE_STATE_CONNECTED != ble_global->state) {
        printf("%s: warning, wrong state %d\n", __FUNCTION__, ble_global->state);
        return;
    }

    printf("%s: event_id %d, bitmap %d\n",
           __FUNCTION__, ble_global->conn_msg.event_id, ble_global->profile_bitmap);

    ble_global->service_discover_state = BLE_SERVICE_DISCOVER_COMPLETE;

    for(uint8_t i = 0;i < BLE_PROFILE_MAX;i++) {
        if(ble_global->event_cb[i] && (0 != (ble_global->profile_bitmap & (1 << i)))) {
            ble_global->event_cb[i](&ble_global->conn_msg);
        }
    }
}

static uint16_t ble_get_service_changed_handle(void)
{
    uint16_t handle = 0;
    if(ble_global && (BLE_SERVICE_DISCOVER_COMPLETE == ble_global->service_discover_state)) {
        ble_service_record_t * record = _lv_ll_get_head(&ble_global->record);
        while(record) {
            printf("%s: service uuid type %d, value %d\n",
                   __FUNCTION__, record->uuid.type, record->uuid.u.uuid_16.value);
            if((LE_UUID_TYPE_16 == record->uuid.type)
               && (LE_ATT_UUID_GENERIC_ATTRIBUTE == record->uuid.u.uuid_16.value)) {
                ble_characteristic_record_t * characteristic
                    = _lv_ll_get_head(&record->characteristic);
                while(characteristic) {
                    printf("%s: characteristic uuid type %d, value %d\n",
                           __FUNCTION__, characteristic->uuid.type,
                           characteristic->uuid.u.uuid_16.value);
                    if((LE_UUID_TYPE_16 == characteristic->uuid.type)
                       && (LE_ATT_UUID_SERVICE_CHANGED == characteristic->uuid.u.uuid_16.value)) {
                        handle = characteristic->value_handle;
                        printf("%s: characteristic->propertis %d, ccc %d\n",
                               __FUNCTION__, characteristic->propertis, characteristic->ccc);
                        break;
                    }
                    characteristic = _lv_ll_get_next(&record->characteristic, characteristic);
                }
                break;
            }
            record = _lv_ll_get_next(&ble_global->record, record);
        }
    }
    printf("%s: handle %d, discover state %d\n",
           __FUNCTION__, handle, ble_global->service_discover_state);
    return handle;
}

static void ble_init_profiles(void)
{
    for(uint8_t i = 0;i < BLE_PROFILE_MAX;i++) {
        switch(i) {
        case BLE_PROFILE_HEART_RATE:
            ble_hrp_init();
            break;
        case BLE_PROFILE_FIND_ME:
            ble_fmp_init();
            break;
        }
    }
}

static void ble_set_adv_data(void)
{
    unsigned char data[31] = {};
    char name[31] = BLE_DEV_NAME;
    int adv_size = 0;
    int total = 0;
    unsigned char flags = 0;

#if BLE_DEV == 0
    struct bt_addr addr = {};
    Hal_Mem_Copy(addr.bytes, appbt_get_leaddr(), 6);
    appbt_le_set_random_address(addr);
#endif

    adv_size = appbt_le_create_adv_data(LE_GAP_TYPE_COMPLETE_NAME,
                                        &data[adv_size], 31 - adv_size,
                                        (unsigned char *)name, strlen(name));
    total += adv_size;
    flags = (1 << 1);
    adv_size = appbt_le_create_adv_data(LE_GAP_TYPE_FLAGS,
                                        &data[adv_size], 31 - adv_size,
                                        &flags, sizeof(flags));
    total += adv_size;
    appbt_le_set_adv_data(data, total);
}

static void ble_set_adv_param(uint16_t min_interval, uint16_t max_interval)
{
    printf("%s: interval min %d, max %d\n", __FUNCTION__, min_interval, max_interval);

    struct bt_task_le_adv_parameters adv = {};
    adv.interval_min = min_interval / 0.625;
    adv.interval_max = max_interval / 0.625;
    adv.advertising_type = LE_ADV_TYPE_IND;
    adv.own_address_type = LE_ADDRESS_TYPE_RANDOM;
    appbt_le_set_adv_parameters(&adv);
    appbt_le_set_adv_enable(1);
}

static void ble_register_service(void)
{
    if(!ble_global) {
        printf("%s: ble is not initiated\n", __FUNCTION__);
        return;
    }

    uint32_t size = 0;
    for(uint8_t i = 0;i < BLE_PROFILE_MAX;i++) {
        if(ble_global->get_cb[i]) {
            size += ble_global->get_cb[i](NULL);
        }
    }

    if(0 == size) {
        printf("%s: no service\n", __FUNCTION__);
        ble_global->is_service_present = false;
        return;
    }

    printf("%s: size of services %d\n", __FUNCTION__, size);

    ble_global->is_service_present = true;

    struct gatt_attr * serv_attrs = Hal_Mem_Alloc(size);
    Hal_Mem_Set(serv_attrs, 0, size);
    uint32_t index = 0;
    uint32_t struct_size = sizeof(struct gatt_attr);
    for(uint8_t i = 0;i < BLE_PROFILE_MAX;i++) {
        if(ble_global->get_cb[i]) {
            index += ble_global->get_cb[i](&serv_attrs[index / struct_size]);
        }  
    }
    appbt_le_register_service(serv_attrs, size / struct_size);
    Hal_Mem_Free(serv_attrs);
}

static void ble_service_record_add(uint16_t start_handle, uint16_t end_handle,
                                   struct le_uuid_common uuid)
{
    if(LE_UUID_TYPE_16 == uuid.type) {
        printf("%s: uuid %04x\n", __FUNCTION__, uuid.u.uuid_16.value);
    } else if(LE_UUID_TYPE_32 == uuid.type) {
        printf("%s: uuid %08x\n", __FUNCTION__, uuid.u.uuid_32.value);
    } else if(LE_UUID_TYPE_128 == uuid.type) {
        printf("%s: 128 uuid\n", __FUNCTION__);
    }

    if(!ble_global) {
        printf("%s: warning, ble is not initiated\n", __FUNCTION__);
        return;
    }

    ble_service_record_t * record = _lv_ll_ins_tail(&ble_global->record);
    Hal_Mem_Set(record, 0, sizeof(ble_service_record_t));
    record->start_handle = start_handle;
    record->end_handle = end_handle;
    Hal_Mem_Copy(&record->uuid, &uuid, sizeof(uuid));
    _lv_ll_init(&record->characteristic, sizeof(ble_characteristic_record_t));
}

static void ble_read_characteristic(void)
{
    if(ble_global->service) {
        appbt_le_read_by_type_request(ble_global->service->start_handle,
                                      ble_global->service->end_handle,
                                      LE_ATT_UUID_CHARC);
        ble_global->service_discover_state = BLE_SERVICE_DISCOVER_CHARACTERISTIC;
    } else {
        ble_read_service_record_complete();
    }
}

static void ble_characteristic_record_add(uint16_t handle, uint8_t propertis[5])
{
    if(!ble_global->service) {
        printf("%s: warning, service is NULL\n", __FUNCTION__);
        return;
    }

    uint8_t properties = propertis[0];
    uint16_t value_handle = (propertis[2] << 8) | propertis[1];
    uint16_t uuid = (propertis[4] << 8) | propertis[3];

    ble_characteristic_record_t * temp = _lv_ll_ins_tail(&ble_global->service->characteristic);

    printf("%s: handle %04x, uuid %04x\n", __FUNCTION__, value_handle, uuid);

    temp->handle = handle;
    temp->value_handle = value_handle;
    temp->propertis = properties;
    temp->uuid.type = 2;
    temp->uuid.u.uuid_16.uuid.type = 2;
    temp->uuid.u.uuid_16.value = uuid;
    temp->ccc = 0;
}

static void ble_discover_ccc(void)
{
    while(ble_global->service) {
        while(ble_global->characteristic) {
            printf("%s: characteristic propertis %02x, uuid type %d, value %04x\n", 
                   __FUNCTION__, ble_global->characteristic->propertis,
                   ble_global->characteristic->uuid.type,
                   ble_global->characteristic->uuid.u.uuid_16.value);
            if((0 != (ble_global->characteristic->propertis & LE_ATT_CHARC_PROP_NOTIFY))
               || (0 != (ble_global->characteristic->propertis & LE_ATT_CHARC_PROP_INDICATE)))
            {
                appbt_le_find_information_request(ble_global->characteristic->value_handle + 1,
                                                  ble_global->characteristic->value_handle + 1);
                ble_global->service_discover_state = BLE_SERVICE_DISCOVER_CCC;
                return;
            }
            ble_global->characteristic = _lv_ll_get_next(&ble_global->service->characteristic,
                                                         ble_global->characteristic);
        }
        ble_global->service = _lv_ll_get_next(&ble_global->record, ble_global->service);
        if(ble_global->service) {
            ble_global->characteristic = _lv_ll_get_head(&ble_global->service->characteristic);
        }
    }
    if(!ble_global->service) {
        ble_read_service_record_complete();
    }
}

static void ble_service_record_free(void)
{
    if(!ble_global) {
        printf("%s: warning, ble_global is NULL\n", __FUNCTION__);
        return;
    }

    ble_global->service = NULL;
    ble_global->characteristic = NULL;
    ble_global->service_discover_state = BLE_SERVICE_DISCOVER_NULL;

    ble_service_record_t * record = _lv_ll_get_head(&ble_global->record);
    while(record) {
        _lv_ll_clear(&record->characteristic);
        record = _lv_ll_get_next(&ble_global->record, record);
    }
    _lv_ll_clear(&ble_global->record);
}

static int32_t ble_att_event_handle(struct bt_task_event * msg)
{
    struct bt_event_le_mtu_exchange * mtu;
    struct bt_event_le_att_connected * connect;
    struct bt_event_le_read_by_group_type_rsp * rsp;
    struct bt_event_le_client_handle_indication * ind;
    struct bt_event_le_common_rsp * find_rsp;
    uint8_t * pdu;
    uint8_t each_size;
    uint32_t i;
    uint16_t start;
    uint16_t end;
    struct le_uuid_common uuid;
    uint16_t value_handle;
    uint16_t handle;

    if(!ble_global) {
        printf("%s: ble is not initiated\n", __FUNCTION__);
        return 0;
    }

    if((BLE_STATE_CONNECTED != ble_global->state)
       && (BTTASK_IND_LE_GATT_CONNECTED != msg->event_id)
       && (BTTASK_IND_LE_GATT_DISCONNECTED != msg->event_id)) {
        printf("%s: not handle msg %d at wrong state %d\n",
               __FUNCTION__, msg->event_id, ble_global->state);
        return 0;
    } else {
        printf("%s: msg->event_id %d\n", __FUNCTION__, msg->event_id);
    }

    switch(msg->event_id) {
    case BTTASK_IND_LE_GATT_CONNECTED:
        Hal_Mem_Copy(&ble_global->conn_info, msg->payload,
                     sizeof(struct bt_event_le_att_connected));
        ble_global->conn_msg.event_type = msg->event_type;
        ble_global->conn_msg.event_id = msg->event_id;
        ble_global->conn_msg.payload_length = msg->payload_length;
        ble_global->conn_msg.payload = &ble_global->conn_info;
        connect = (struct bt_event_le_att_connected *)msg->payload;
        printf("%s: add connect %d, role %d, %02x%02x%02x%02x%02x%02x\n",
               __FUNCTION__, connect->acl_handle, connect->role,
               connect->addr.bytes[0], connect->addr.bytes[1], 
               connect->addr.bytes[2], connect->addr.bytes[3], 
               connect->addr.bytes[4], connect->addr.bytes[5]);

        appbt_le_set_adv_enable(0);

        if(LE_ATT_SLAVE == connect->role) {
            ble_global->is_slave = true;
        } else {
            if(0 == ble_global->profile_bitmap) {
                printf("%s: warning, unknown conn est\n", __FUNCTION__);
            }
        }

        printf("%s: rcv connected msg in state %d, profile_bitmap %d\n",
               __FUNCTION__, ble_global->state, ble_global->profile_bitmap);

        ble_global->state = BLE_STATE_CONNECTED;

        if(ble_global->fast_conn_timer) {
            uos_timer_delete(ble_global->fast_conn_timer);
            ble_global->fast_conn_timer = NULL;
        }

        if(LE_ATT_MASTER == connect->role) {
            appbt_le_mtu_request(connect->acl_handle);
            ble_global->service_discover_state = BLE_SERVICE_DISCOVER_MTU;
            printf("%s: start mtu\n", __FUNCTION__);
        }
        break;

    case BTTASK_IND_LE_GATT_DISCONNECTED:
        if((BLE_STATE_CONNECTING != ble_global->state)
           && (BLE_STATE_CONNECTED != ble_global->state)
           && (BLE_STATE_DISCONNECTING != ble_global->state)) {
            printf("%s: rcv disc msg in wrong state %d\n", __FUNCTION__, ble_global->state);
            break;
        }

        ble_global->state = BLE_STATE_IDLE;

        ble_global->is_slave = false;

        ble_service_record_free();

        if((0 == ble_global->profile_bitmap) && ble_global->is_service_present) {
            ble_set_adv_param(20, 30);
            ble_start_fast_conn_timer();
        }
        break;

    case BTTASK_IND_LE_CLIENT_MTU_EXCHANGED:
        mtu = (struct bt_event_le_mtu_exchange *)msg->payload;
        printf("%s: client MTU exchaneg done %d, discover state %d\n",
               __FUNCTION__, mtu->mut, ble_global->service_discover_state);

        if(BLE_SERVICE_DISCOVER_MTU != ble_global->service_discover_state) {
            printf("%s: rcv mtu exchanged at wrong state\n", __FUNCTION__);
            break;
        }

        appbt_le_read_by_group_type_request(0x0001, 0xffff, LE_ATT_UUID_PRIMARY);
        ble_global->service_discover_state = BLE_SERVICE_DISCOVER_PRIMARY;
        break;

    case BTTASK_IND_LE_CLIENT_READ_BY_GROUP_TYPE_RSP:
        if(BLE_SERVICE_DISCOVER_PRIMARY != ble_global->service_discover_state) {
            printf("%s: rcv read by group type rsp at wrong state %d\n",
                   __FUNCTION__, ble_global->service_discover_state);
            break;
        }

        rsp = (struct bt_event_le_read_by_group_type_rsp *)msg->payload;
        pdu = rsp->value;
        each_size = pdu[0];

        printf("%s: read by group type rsp. each size %d\n", __FUNCTION__, each_size);

        if(0 != ((rsp->size - 1) % each_size)) {
            printf("%s: bad data\n", __FUNCTION__);
            ble_read_service_record_complete();
            break;
        }

        for(i = 0; i < (rsp->size - 1) / each_size; i++) {
            start = (pdu[2 + i * each_size] << 8) | pdu[1 + i * each_size];
            end = (pdu[4 + i * each_size] << 8) | pdu[3 + i * each_size];

            switch(each_size - 4) {
            case LE_UUID_TYPE_16:
                uuid.u.uuid_16.uuid.type = uuid.type = LE_UUID_TYPE_16;
                uuid.u.uuid_16.value = pdu[6 + i * each_size] << 8 | pdu[5 + i * each_size];
                break;

            case LE_UUID_TYPE_32:
                uuid.u.uuid_32.uuid.type = uuid.type = LE_UUID_TYPE_32;
                memcpy(&uuid.u.uuid_32.value, &pdu[5 + i * each_size], 4);
                break;

            case LE_UUID_TYPE_128:
                uuid.u.uuid_128.uuid.type = uuid.type = LE_UUID_TYPE_128;
                memcpy(&uuid.u.uuid_128.value, &pdu[5 + i * each_size], 16);
                break;
            }
            ble_service_record_add(start, end, uuid);
        }

        if(0xffff == end) {
            ble_global->service = _lv_ll_get_head(&ble_global->record);
            ble_read_characteristic();
        } else {
            appbt_le_read_by_group_type_request(end + 1, 0xffff, LE_ATT_UUID_PRIMARY);
        }

        break;

    case BTTASK_IND_LE_ERROR_RESPONSE:
        if(BLE_SERVICE_DISCOVER_PRIMARY == ble_global->service_discover_state) {
            ble_global->service = _lv_ll_get_head(&ble_global->record);
            ble_read_characteristic();
        } else if(BLE_SERVICE_DISCOVER_CHARACTERISTIC == ble_global->service_discover_state) {
            ble_global->service = _lv_ll_get_head(&ble_global->record);
            if(ble_global->service) {
                ble_global->characteristic = _lv_ll_get_head(&ble_global->service->characteristic);
            }
            ble_discover_ccc();
        } else if(BLE_SERVICE_DISCOVER_CCC == ble_global->service_discover_state) {
            ble_read_service_record_complete();
        } else {
            printf("%s: rcv err rsp at wrong state %d\n",
                   __FUNCTION__, ble_global->service_discover_state);
        }
        break;

    case BTTASK_IND_LE_CLIENT_READ_BY_TYPE_RSP:
        if(!ble_global->service) {
            printf("%s: warning, service is NULL\n", __FUNCTION__);
            break;
        }

        if(BLE_SERVICE_DISCOVER_CHARACTERISTIC != ble_global->service_discover_state) {
            printf("%s: rcv read by type rsp at wrong state %d\n",
                   __FUNCTION__, ble_global->service_discover_state);
            break;
        }

        rsp = (struct bt_event_le_read_by_group_type_rsp *)msg->payload;
        pdu = rsp->value;
        each_size = pdu[0];

        printf("%s: read by type rsp. each size %d\n", __FUNCTION__, each_size);

        if(0 != ((rsp->size - 1) % each_size)) {
            printf("%s: bad data\n", __FUNCTION__);
            ble_read_service_record_complete();
            break;
        }

        for(i = 0; i < (rsp->size - 1) / each_size; i++)
        {
            handle = (pdu[2 + i * each_size] << 8) | pdu[1 + i * each_size];
            value_handle = (pdu[5 + i * each_size] << 8) | pdu[4 + i * each_size];

            ble_characteristic_record_add(handle, &pdu[3 + i * each_size]);
        }

        if(value_handle && (value_handle < ble_global->service->end_handle)) {
            appbt_le_read_by_type_request(value_handle, ble_global->service->end_handle,
                                          LE_ATT_UUID_CHARC);
            break;
        }

        ble_global->service = _lv_ll_get_next(&ble_global->record, ble_global->service);
        if(ble_global->service) {
            ble_read_characteristic();
        } else {
            ble_global->service = _lv_ll_get_head(&ble_global->record);
            if(ble_global->service) {
                ble_global->characteristic = _lv_ll_get_head(&ble_global->service->characteristic);
            }
            ble_discover_ccc();
        }
        break;

    case BTTASK_IND_LE_CLIENT_FIND_INFOMATION_RSP:
        if(!ble_global->characteristic || !ble_global->service) {
            printf("%s: warning, characteristic is NULL\n", __FUNCTION__);
            break;
        }

        if(BLE_SERVICE_DISCOVER_CCC != ble_global->service_discover_state) {
            printf("%s: rcv find info rsp at wrong state %d\n",
                   __FUNCTION__, ble_global->service_discover_state);
            break;
        }

        find_rsp = (struct bt_event_le_common_rsp *)msg->payload;
        pdu = find_rsp->value;

        printf("%s: find info rsp %s UUID\n",
               __FUNCTION__, pdu[0] == 1 ? "16-bit" : "128-bit");

        /* 0x2902: client characteristic configuration */
        if((1 == pdu[0]) && (0x02 == pdu[3]) && (0x29 == pdu[4])) {
            ble_global->characteristic->ccc = (pdu[2] << 8) | pdu[1];
        }

        ble_global->characteristic = _lv_ll_get_next(&ble_global->service->characteristic,
                                                     ble_global->characteristic);
        if(!ble_global->characteristic) {
            ble_global->service = _lv_ll_get_next(&ble_global->record, ble_global->service);
            if(ble_global->service) {
                ble_global->characteristic = _lv_ll_get_head(&ble_global->service->characteristic);
            }
        }
        ble_discover_ccc();
        break;

    case BTTASK_IND_LE_CLIENT_HANDLE_INDIATION:
        ind = (struct bt_event_le_client_handle_indication*)msg->payload;
        pdu = ind->value;

        printf("%s: handle indication at ACL %d, UUID handler %d, size %d\n", 
               __FUNCTION__, ind->acl_handle, ind->handle, ind->size);
        for(i = 0; i < ind->size; i++)
            printf("%02x,", pdu[i]);
        printf("\n");

        if(ble_get_service_changed_handle() == ind->handle) {
            if(4 != ind->size) {
                printf("%s: warning, invalid size %d for service changed\n",
                       __FUNCTION__, ind->size);
                break;
            }

            printf("%s: service changed, handle %04x ~ %04x\n",
                   __FUNCTION__, pdu[0] + (pdu[1] << 8), pdu[2] + (pdu[3] << 8));

            // service discovery process
            ble_service_record_free();
            appbt_le_read_by_group_type_request(0x0001, 0xffff, LE_ATT_UUID_PRIMARY);
            ble_global->service_discover_state = BLE_SERVICE_DISCOVER_PRIMARY;
        }
        break;

    default:
        break;
    }

    for(uint8_t i = 0;i < BLE_PROFILE_MAX;i++) {
        if((BTTASK_IND_LE_GATT_CONNECTED == msg->event_id)
           && (BLE_SERVICE_DISCOVER_COMPLETE != ble_global->service_discover_state)
           && (0 != (ble_global->profile_bitmap & (1 << i)))) {
            // not inform client until the completion of the service discovery
            continue;
        }
        if(ble_global->event_cb[i]) {
            ble_global->event_cb[i](msg);
        }
    }

    if((BTTASK_IND_LE_GATT_DISCONNECTED == msg->event_id) && ble_global->profile_bitmap) {
        // reconnect for the clients
        if(0 == appbt_le_scan(LE_ACTIVE_SCAN,
                              0x60, // interval - 50ms
                              0x30, // windown - 30ms
                              LE_ADDRESS_TYPE_RANDOM,
                              ble_scan_event_handle)) {
            ble_global->state = BLE_STATE_SCAN;

            uos_timer_create(&ble_global->fast_scan_timer);
            uos_timer_start(ble_global->fast_scan_timer, MS_TO_TICKS(30000), 0,
                            ble_fast_scan_timer_cb, 0);
        } else {
            printf("%s: scan failed\n", __FUNCTION__);
        }
    }

    return 0;
}

static void ble_scan_event_handle(struct bt_event_le_scan_event * scan)
{
    if(!ble_global) {
        printf("%s: warning, ble is not initiated\n", __FUNCTION__);
        return;
    }

    if(BLE_STATE_SCAN != ble_global->state) {
        printf("%s: warning, wrong state %d\n", __FUNCTION__, ble_global->state);
        return;
    }

    uint8_t index = 0;
    uint32_t total_size = (uint32_t)scan->length;
    char name[31] = {};

    while((0 < total_size) && (LE_ADV_TYPE_IND == scan->event_type)) {
        uint32_t length = scan->data[index];
        uint32_t type = scan->data[index + 1];

        switch(type) {
        case LE_GAP_TYPE_SHORT_NAME:
        case LE_GAP_TYPE_COMPLETE_NAME:
            Hal_Mem_Copy(name, &scan->data[index + 2], length - 1);
            printf("%s: name %s, length %d\n", __FUNCTION__, name, length);

            if(strstr(name, BLE_DEV_SCAN_NAME)) {
                appbt_le_scan_stop();
                appbt_le_connect(scan->address, scan->address_type);
                ble_global->state = BLE_STATE_CONNECTING;

                if(ble_global->fast_conn_timer) {
                    uos_timer_delete(ble_global->fast_conn_timer);
                    ble_global->fast_conn_timer = NULL;
                }

                if(ble_global->fast_scan_timer) {
                    uos_timer_delete(ble_global->fast_scan_timer);
                    ble_global->fast_scan_timer = NULL;
                }
            }
            break;

        default:
            break;
        }
        index += (length + 1);
        total_size -= (length + 1);
    }
}

static void ble_fast_conn_timer_cb(uint32_t param)
{
    if(BLE_STATE_IDLE != ble_global->state) {
        printf("%s: warning, wrong state %d\n", __FUNCTION__, ble_global->state);
        return;
    }

    uos_timer_delete(ble_global->fast_conn_timer);
    ble_global->fast_conn_timer = NULL;

    appbt_le_set_adv_enable(0);
    ble_set_adv_param(1000, 2500);
}

static void ble_start_fast_conn_timer(void)
{
    if(ble_global->fast_conn_timer) return;

    uos_timer_create(&ble_global->fast_conn_timer);
    uos_timer_start(ble_global->fast_conn_timer, MS_TO_TICKS(30000), 0, ble_fast_conn_timer_cb, 0);
}

static void ble_fast_scan_timer_cb(uint32_t param)
{
    appbt_le_scan_stop();
    appbt_le_scan(LE_ACTIVE_SCAN,
                      0x1000, // interval - 2.56s
                      0x12,   // windown - 11.25ms
                      LE_ADDRESS_TYPE_RANDOM,
                      ble_scan_event_handle);

    uos_timer_delete(ble_global->fast_scan_timer);
    ble_global->fast_scan_timer = NULL;
}

#endif
